package com.ego.search.service;

import com.ego.common.pojo.JsonUtils;
import com.ego.common.pojo.NumberUtils;
import com.ego.common.pojo.PageResult;

import com.ego.exception.enums.ExceptionEnum;
import com.ego.exception.exception.EgoException;
import com.ego.item.entity.*;
import com.ego.search.entity.Goods;
import com.ego.search.entity.GoodsRepository;
import com.ego.search.entity.SearchRequest;
import com.ego.search.entity.SearchResponse;
import com.ego.search.feign.BrandClient;
import com.ego.search.feign.CategoryClient;
import com.ego.search.feign.GoodsClient;
import com.ego.search.feign.SpecFeignClient;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedHistogram;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.metrics.ParsedStats;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SearchService {
    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private ElasticsearchRestTemplate restTemplate;

    @Resource
    private CategoryClient categoryClient;

    @Resource
    private BrandClient brandClient;

    @Resource
    private SpecFeignClient specClient;

    @Resource
    private GoodsClient goodsClient;

    @Resource
    private GoodsRepository goodsRepository;


    public Goods buildGoods(SpuBO spuBO) {
        Goods goods = new Goods();
        try {
            StringBuffer buffer = new StringBuffer();
            String all = buffer.append(spuBO.getTitle()).append(" ").append(spuBO.getCategoryNames()).append(" ").append(spuBO.getBrandName()).toString();
            SpuDetail spuDetail = spuBO.getSpuDetail();
            String specJson = spuDetail.getSpecifications();
            //解析规格参数，将可搜索的规格参数存入specs中
            Map<String, Object> specs = new HashMap<>();
            List<Map<String, Object>> specList = objectMapper.readValue(specJson, new TypeReference<List<Map<String, Object>>>() {});
            specList.forEach(spec -> {
                List<Map<String, Object>> params = (List<Map<String, Object>>) spec.get("params");
                params.forEach(param -> {
                    if ((Boolean) param.get("searchable")) {
                        Object v = param.get("v");
                        if (v != null) {
                            specs.put(param.get("k").toString(), v);
                        } else {
                            specs.put(param.get("k").toString(), param.get("options"));
                        }
                    }
                });
            });

            String skus = objectMapper.writeValueAsString(spuBO.getSkus());

            List<Long> price = spuBO.getSkus().stream().map(sku -> sku.getPrice()).collect(Collectors.toList());

            goods=Goods.builder()

                    .brandId(spuBO.getBrandId())
                    .cid1(spuBO.getCid1())
                    .cid2(spuBO.getCid2())
                    .cid3(spuBO.getCid3())
                    .createTime(spuBO.getCreateTime())
                    .id(spuBO.getId())
                    .subTitle(spuBO.getSubTitle())
                    .all(all)
                    .specs(specs).
                            skus(skus)
                    .price(price)
                    .build();
        } catch (Exception e) {
            e.printStackTrace();
        }


        return goods;
    }

    public PageResult<Goods> search(SearchRequest searchRequest) {
        PageResult<Goods> result = new PageResult<>();
        String key = searchRequest.getKey();
        Integer page = searchRequest.getPage();
        // 判断是否有搜索条件，如果没有，直接返回null。不允许搜索全部商品
        if (StringUtils.isBlank(key)) {
            EgoException.error(log, ExceptionEnum.GOODS_PAGE_ERROR);
        }

        NativeSearchQueryBuilder queryBuilder=new NativeSearchQueryBuilder();
        //查询条件
        queryBuilder.withQuery(QueryBuilders.matchQuery("all", key));
        //筛选需要字段
        queryBuilder.withSourceFilter(new FetchSourceFilter(
                new String[]{"id","skus","subTitle"}, null));
        //指定分页条件
        queryBuilder.withPageable(PageRequest.of(page - 1, result.getSize()));
        SearchHits<Goods> searchHits = restTemplate.search(queryBuilder.build(), Goods.class);
        result.setTotal(searchHits.getTotalHits());
        List<Goods> goodsList = searchHits.getSearchHits().stream().map(goodsSearchHit -> goodsSearchHit.getContent()).collect(Collectors.toList());
        result.setItems(goodsList);

        return result;

    }

    public SearchResponse page(SearchRequest searchRequest) {
        SearchResponse result = new SearchResponse();

        String key = searchRequest.getKey();
        Integer pageNo = searchRequest.getPage();
        if (StringUtils.isEmpty(key)) {
            EgoException.error(log, ExceptionEnum.GOODS_PAGE_ERROR);
        }
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //指定字段
        nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","skus","subTitle"},null));
        //指定查询条件
        QueryBuilder basicQuery = QueryBuilders.matchQuery("all", key);
        nativeSearchQueryBuilder.withQuery(basicQuery);
        //指定分页条件
        nativeSearchQueryBuilder.withPageable(PageRequest.of(pageNo-1,result.getSize()));
        //添加固定字段聚合(cid3,brandId)
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("categoryAgg").field("cid3"));
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("brandAgg").field("brandId"));

        //执行搜索
        SearchHits<Goods> searchHits = restTemplate.search(nativeSearchQueryBuilder.build(), Goods.class);

        //解析固定字段聚合结果
        List<Category> categoryList = getCategoryList(searchHits);
        List<Brand> brandList = getBrandList(searchHits);

        result.setCategories(categoryList);
        result.setBrands(brandList);

        if (categoryList.size() > 0) {
            List<Map<String,Object>> specsList = getSpecsList(basicQuery,categoryList.get(0));
            result.setSpecs(specsList);
        }

        result.setTotal(searchHits.getTotalHits());
        result.setItems(searchHits.getSearchHits().stream().map(searchHit -> searchHit.getContent()).collect(Collectors.toList()));

        return result;
    }

    private List<Map<String, Object>> getSpecsList(QueryBuilder basicQuery, Category category) {
        List<Map<String, Object>> result = new ArrayList<>();
        //1.查询出第一个类别的规格参数
        String specifications = specClient.querySpecByCid(category.getId()).getBody();
        //2.区分出字符型和数字型规格参数
        Set<String> strSpecs = new HashSet<>();
        Map<String,String> numSpecs = new HashMap<>();
        List<Map<String, Object>> groupList = JsonUtils.nativeRead(specifications, new TypeReference<List<Map<String, Object>>>() {
        });
        groupList.forEach(group->{
            List<Map<String,Object>> params =  (List<Map<String,Object>>)group.get("params");
            params.forEach(param->{
                Boolean numerical = (Boolean)param.get("numerical");
                Boolean searchable = (Boolean)param.get("searchable");
                String k = param.get("k").toString();
                //判断是否可搜索
                if (searchable == null || !searchable) {
                    return;
                }
                //判断是否为数字型
                if (numerical != null && numerical) {
                    //如果为数字型，需要获取单位unit
                    numSpecs.put(k, param.get("unit").toString());
                } else {
                    //如果为字符型，只要k
                    strSpecs.add(k);
                }
            });
        });
        //3.字符型采用词条聚合
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        nativeSearchQueryBuilder.withQuery(basicQuery);
        strSpecs.forEach(spec->{
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(spec).field("specs." + spec + ".keyword"));
        });

        //4.计算出每个数字型规格参数的间隔
        Map<String, Double> numSpecIntervalMap = getNumSpecIntervalMap(basicQuery,numSpecs);
        //5.数字型采用阶梯聚合
        numSpecs.keySet().forEach(spec->{
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.histogram(spec).field("specs."+spec).interval(numSpecIntervalMap.get(spec)));
        });
        //6.执行聚合查询
        SearchHits<Goods> searchHits = restTemplate.search(nativeSearchQueryBuilder.build(), Goods.class);

        //7.解析聚合结果
        //7.1.解析字符型聚合结果
        strSpecs.forEach(spec->{
            ParsedStringTerms aggregation = searchHits.getAggregations().get(spec);

            Map<String, Object> filter = new HashMap<>();
            filter.put("name", spec);
            //将每个bucket转成前端的option对象，并且option放进一个新的list
            List<Map<String, String>> options = aggregation.getBuckets().stream()
                    .map(bucket -> {
                        Map<String, String> option = new HashMap<>();
                        String key = bucket.getKeyAsString();
                        option.put("name", key);
                        return option;
                    })
                    .collect(Collectors.toList());

            filter.put("options", options);
            result.add(filter);
        });
        //7.2.解析数字型聚合结果
        numSpecs.forEach((spec,unit)->{
            ParsedHistogram aggregation = searchHits.getAggregations().get(spec);
            List<Map<String, String>> options = aggregation.getBuckets().stream()
                    .map(bucket -> {
                        Map<String, String> option = new HashMap<>();
                        Double begin = (Double) bucket.getKey();
                        Double end = begin+numSpecIntervalMap.get(spec);
                        String name = null;
                        //判断是否是整型，如果是就不要小数点
                        if (begin.intValue() == begin && end.intValue() == end) {
                            name = begin.intValue() + "_" + end.intValue();
                        } else {
                            name =NumberUtils.scale(begin,1) + "_" + NumberUtils.scale(end,1);
                        }

                        option.put("name", name);
                        return option;
                    }).collect(Collectors.toList());

            Map<String, Object> filter = new HashMap<>();
            filter.put("name",spec);
            filter.put("options",options);
            filter.put("unit",unit);
            result.add(filter);
        });
        return result;
    }

    /**
     * 聚合出所有数字型参数间隔
     * @param basicQuery
     * @param numSpecs
     * @return
     */
    private Map<String, Double> getNumSpecIntervalMap(QueryBuilder basicQuery, Map<String, String> numSpecs) {
        Map<String, Double> result = new HashMap<>();

        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        nativeSearchQueryBuilder.withQuery(basicQuery);
        //添加聚合条件
        numSpecs.keySet().forEach(spec->{
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.stats(spec).field("specs." + spec));
        });
        //执行聚合查询
        SearchHits<Goods> searchHits = restTemplate.search(nativeSearchQueryBuilder.build(), Goods.class);
        //解析聚合结果
        numSpecs.keySet().forEach(spec->{
            ParsedStats aggregation = searchHits.getAggregations().get(spec);
            double interval = NumberUtils.getInterval(aggregation.getMin(), aggregation.getMax(), aggregation.getSum());
            result.put(spec, interval);
        });
        return result;
    }

    private List<Brand> getBrandList(SearchHits<Goods> searchHits) {
        ParsedLongTerms brandAgg = searchHits.getAggregations().get("brandAgg");
        List<Long> brandIdList = brandAgg.getBuckets().stream().map(bucket -> bucket.getKeyAsNumber().longValue()).collect(Collectors.toList());
        return brandClient.queryBrandByIds(brandIdList).getBody();
    }

    private List<Category> getCategoryList(SearchHits<Goods> searchHits) {
        ParsedLongTerms categoryAgg = searchHits.getAggregations().get("categoryAgg");
        List<Long> categoryIdList = categoryAgg.getBuckets().stream().map(bucket -> bucket.getKeyAsNumber().longValue()).collect(Collectors.toList());
        return categoryClient.queryListByIds(categoryIdList).getBody();
    }


    public void createIndex(Long id) throws IOException {

        SpuBO spubo = this.goodsClient.queryGoodsById(id);
        // 构建商品
        Goods goods = this.buildGoods(spubo);

        // 保存数据到索引库
        this.goodsRepository.save(goods);
    }

    public void deleteIndex(Long id) {
        this.goodsRepository.deleteById(id);
    }

}
